package com.util;

import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.io.*;

public class PushLiveUtil extends Thread {
    private static final Logger log = LoggerFactory.getLogger(PushLiveUtil.class);

    private PipedInputStream pis = new PipedInputStream();
    private PipedOutputStream pos = new PipedOutputStream();
    private String rtmpUrl;
    private volatile boolean running = true;

    public PipedInputStream getInputStream() {
        return pis;
    }

    public PushLiveUtil(String rtmpUrl) {
        this.rtmpUrl = rtmpUrl;
        try {
            // 使用管道流实现线程间通信
            pos.connect(pis);
        } catch (IOException e) {
            log.info("pos connect pis error.{}", e.getMessage());
        }
    }

    /**
     * 将视频流写入管道
     */
//    public void onMediaStream(byte[] data) {
//        try {
//            log.info("receive data...");
//            pos.write(data);
//        } catch (IOException e) {
//            log.error("write video data error.{}", e.getMessage());
//            try {
//                pos.close();
//            } catch (IOException ex) {
//                log.error("pos close error.{}", ex.getMessage());
//            }
//        }
//    }
    public void onMediaStream(File file) {
        new Thread(() -> {
            while (running) {
                try {
                    // 将外部的逻辑拿进来
                    FileInputStream fi = new FileInputStream(file);
                    byte[] buffer = new byte[(int) file.length()];
                    int offset = 0;
                    int numRead = 0;
                    while (offset < buffer.length && (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
                        offset += numRead;
                    }
                    fi.close();

                    // 使用管道流实现线程间通信
                    log.info("receive data...");
                    pos.write(buffer);
                } catch (IOException e) {
                    log.info("pos connect pis error.{}", e.getMessage());
                    try {
                        pos.close();
                    } catch (IOException ex) {
                        log.error("pos close error.{}", ex.getMessage());
                    }
                }
            }
        }).start();
    }

    @Override
    public void run() {
        recordPushWithFormat();
    }

    /**
     * 转流器, 指定format
     */
    public void recordPushWithFormat() {
        long startTime = 0;
        long videoTS;

        try {
            log.info("grabber start ... {} > {}");
            // 从管道流中读取视频流
            // maximumSize 设置为0，不设置会阻塞
            FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(pis, 0);
            // 此配置以减少启动时间！若不设置，启动最起码半分钟；
            // 类似一个缓冲区，用来检测流的正确性，值越小，启动越快
            grabber.setOption("timeout", "1000000");
            // 设置读取的最大数据，单位字节
            grabber.setOption("probesize", "1024");
            // 设置分析的最长时间，单位微秒
            grabber.setOption("analyzeduration", "100000");
//            grabber.setFrameRate(24);
//            grabber.setVideoBitrate(2000000);
            // 阻塞式，直到通道有数据
            grabber.start();
            log.info("grabber start success. and start recorder ... {} > {}");

            // 获取推流地址对应的recorder，用于录制视频，rtmp对应format是flv
            FrameRecorder rtmpRecorder = new FFmpegFrameRecorder(rtmpUrl, 720, 480);
            rtmpRecorder.setInterleaved(true);
//            rtmpRecorder.setVideoOption("tune", "zerolatency");
//            rtmpRecorder.setVideoOption("preset", "ultrafast");
//            rtmpRecorder.setVideoOption("crf", "28");
//            rtmpRecorder.setVideoBitrate(2000000);  //码率 越大越清晰
            rtmpRecorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            rtmpRecorder.setFormat("flv");  //flv rtsp
            rtmpRecorder.setFrameRate(grabber.getFrameRate());  //帧率 30fps 每秒包含的帧数 24-30越大越流畅
//            rtmpRecorder.setGopSize(60);    //30*2 每60帧存在一个关键帧

            //开启录制器
            try {
                rtmpRecorder.start();
            } catch (Exception e) {
                try {
                    //尝试重启录制器
                    rtmpRecorder.stop();
                    rtmpRecorder.start();
                } catch (Exception e1) {
                    e.printStackTrace();
                    e1.printStackTrace();
                }
            }

            Frame grabframe;
            // 从视频流中捕获帧以录制视频
            while (running && (grabframe = grabber.grab()) != null) {
                if (startTime == 0) {
                    startTime = System.currentTimeMillis();
                }
                videoTS = 1000 * (System.currentTimeMillis() - startTime);
                // 推流到rtmp server
                if (videoTS > rtmpRecorder.getTimestamp()) {
                    rtmpRecorder.setTimestamp(videoTS);
                }
                rtmpRecorder.record(grabframe);
            }
        } catch (Exception e) {
            log.error("record push error.", e);
        } finally {
            try {
                pis.close();
            } catch (IOException e) {
                log.error("pis close error.{}", e.getMessage());
            }
        }
    }


    // 获取摄像头方法
    public static void getRecordPush(String outputPath, int v_rs) throws Exception {
        //创建采集器
        OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);  //本地摄像头默认为0
        //开启采集器
        try {
            grabber.start();
        } catch (Exception e) {
            try {
                grabber.restart();  //一次重启尝试
            } catch (Exception e2) {
                throw e;
            }
        }
        //转换器
        OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
        //获取一帧
        Frame grabframe = grabber.grab();
//        IplImage grabbedImage = null;
//        if (grabframe != null) {
//            //将这一帧转换为IplImage
//            grabbedImage = converter.convert(grabframe);
//        }
        //创建录制器
        FrameRecorder recorder;
        recorder = FrameRecorder.createDefault(outputPath, 1280, 720);   //输出路径，画面高，画面宽
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);  //设置编码格式
        recorder.setFormat("flv");
        recorder.setFrameRate(v_rs);
        recorder.setGopSize(v_rs);

        //开启录制器
        try {
            recorder.start();
        } catch (java.lang.Exception e) {
            try {
                if (recorder != null) {  //尝试重启录制器
                    recorder.stop();
                    recorder.start();
                }
            } catch (java.lang.Exception e1) {
                e.printStackTrace();
            }
        }

        //直播效果展示窗口
        CanvasFrame frame = new CanvasFrame("主播-菜鸡-德华", CanvasFrame.getDefaultGamma() / grabber.getGamma());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setAlwaysOnTop(true);

        //推流
        while (frame.isVisible() && (grabframe = grabber.grab()) != null) {
            frame.showImage(grabframe);   //展示直播效果
//            grabbedImage = converter.convert(grabframe);
//            Frame rotatedFrame = converter.convert(grabbedImage);
            if (grabframe != null) {
                recorder.record(grabframe);
            }
            Thread.sleep(50);  //50毫秒/帧
        }
    }


    /**
     * 读取指定的mp4文件，推送到SRS服务器
     * <p>
     * //     * @param sourceFilePath 视频文件的绝对路径
     * //     * @param PUSH_ADDRESS   推流地址
     *
     * @throws Exception
     */
    public static void grabAndPush(String MP4_FILE_PATH, String SRS_PUSH_ADDRESS) throws Exception {
        // ffmepg日志级别
        avutil.av_log_set_level(avutil.AV_LOG_ERROR);
        FFmpegLogCallback.set();

        // 实例化帧抓取器对象，将文件路径传入
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(MP4_FILE_PATH);
        long startTime = System.currentTimeMillis();
        log.info("开始初始化帧抓取器");
        // 初始化帧抓取器，例如数据结构（时间戳、编码器上下文、帧对象等），
        // 如果入参等于true，还会调用avformat_find_stream_info方法获取流的信息，放入AVFormatContext类型的成员变量oc中
        grabber.start(true);
        log.info("帧抓取器初始化完成，耗时[{}]毫秒", System.currentTimeMillis() - startTime);

        // grabber.start方法中，初始化的解码器信息存在放在grabber的成员变量oc中
        AVFormatContext avFormatContext = grabber.getFormatContext();
        // 文件内有几个媒体流（一般是视频流+音频流）
        int streamNum = avFormatContext.nb_streams();
        // 没有媒体流就不用继续了
        if (streamNum < 1) {
            log.error("文件内不存在媒体流");
            return;
        }
        // 取得视频的帧率
        int frameRate = (int) Math.round(grabber.getVideoFrameRate());
        log.info("视频帧率[{}]，视频时长[{}]秒，媒体流数量[{}]",
                frameRate,
                avFormatContext.duration() / 1000000,
                avFormatContext.nb_streams());
        // 遍历每一个流，检查其类型
        for (int i = 0; i < streamNum; i++) {
            AVStream avStream = avFormatContext.streams(i);
            AVCodecParameters avCodecParameters = avStream.codecpar();
            log.info("流的索引[{}]，编码器类型[{}]，编码器ID[{}]", i, avCodecParameters.codec_type(), avCodecParameters.codec_id());
        }

        // 视频宽度
        int frameWidth = grabber.getImageWidth();
        // 视频高度
        int frameHeight = grabber.getImageHeight();
        // 音频通道数量
        int audioChannels = grabber.getAudioChannels();
        log.info("视频宽度[{}]，视频高度[{}]，音频通道数[{}]",
                frameWidth,
                frameHeight,
                audioChannels);
        // 实例化FFmpegFrameRecorder，将SRS的推送地址传入
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(SRS_PUSH_ADDRESS,
                frameWidth,
                frameHeight,
                audioChannels);
        // 设置编码格式
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        // 设置封装格式
        recorder.setFormat("flv");
        // 一秒内的帧数
        recorder.setFrameRate(frameRate);
        // 两个关键帧之间的帧数
        recorder.setGopSize(frameRate);
        // 设置音频通道数，与视频源的通道数相等
        recorder.setAudioChannels(grabber.getAudioChannels());

        startTime = System.currentTimeMillis();
        log.info("开始初始化帧抓取器");
        // 初始化帧录制器，例如数据结构（音频流、视频流指针，编码器），
        // 调用av_guess_format方法，确定视频输出时的封装方式，
        // 媒体上下文对象的内存分配，
        // 编码器的各项参数设置
        recorder.start();
        log.info("帧录制初始化完成，耗时[{}]毫秒", System.currentTimeMillis() - startTime);
        Frame frame;
        startTime = System.currentTimeMillis();
        log.info("开始推流");
        long videoTS = 0;
        int videoFrameNum = 0;
        int audioFrameNum = 0;
        int dataFrameNum = 0;
        // 假设一秒钟15帧，那么两帧间隔就是(1000/15)毫秒
        int interVal = 1000 / frameRate;
        // 发送完一帧后sleep的时间，不能完全等于(1000/frameRate)，不然会卡顿，
        // 要更小一些，这里取八分之一
        interVal /= 8;
        System.out.println("开始的时间戳" + recorder.getTimestamp());

        // 持续从视频源取帧
        while (null != (frame = grabber.grab())) {
            // 时间戳
            recorder.setTimestamp(videoTS);
            videoTS = 1000 * (System.currentTimeMillis() - startTime);
            // 有图像，就把视频帧加一
            if (null != frame.image) {
                videoFrameNum++;
            }
            // 有声音，就把音频帧加一
            if (null != frame.samples) {
                audioFrameNum++;
            }
            // 有数据，就把数据帧加一
            if (null != frame.data) {
                dataFrameNum++;
            }
            // 取出的每一帧，都推送到SRS
            recorder.record(frame);
            // 停顿一下再推送
            Thread.sleep(interVal);
        }

        log.info("推送完成，视频帧[{}]，音频帧[{}]，数据帧[{}]，耗时[{}]秒",
                videoFrameNum,
                audioFrameNum,
                dataFrameNum,
                (System.currentTimeMillis() - startTime) / 1000);
        // 关闭帧录制器
        recorder.close();
        // 关闭帧抓取器
        grabber.close();
    }

}
